﻿using Autofac;
using Autofac.Core;
using Castle.DynamicProxy;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web;

namespace Odn.Infrastructure.DependencyInjection
{
    public class AutofacEngine : IEngine
    {
        #region Fields

        private ContainerManager _containerManager;

        #endregion

        #region Ctor

        /// <summary>
        ///     Creates an instance of the content engine using default settings and configuration.
        /// </summary>
        public AutofacEngine()
            : this(EventBroker.Instance, new ContainerConfigurer())
        {
        }

        public AutofacEngine(EventBroker broker, ContainerConfigurer configurer)
        {
            //var config = new NopConfig() { DynamicDiscovery = true, EngineType = string.Empty, ThemeBasePath = "~/Themes/" }; //ConfigurationManager.GetSection("NopConfig") as NopConfig;
            InitializeContainer(configurer, broker);
        }

        #endregion

        #region Utilities

        private void RunStartupTasks()
        {
            var typeFinder = _containerManager.Resolve<ITypeFinder>();
            IEnumerable<Type> startUpTaskTypes = typeFinder.FindClassesOfType<IStartupTask>();
            var startUpTasks = new List<IStartupTask>();
            foreach (Type startUpTaskType in startUpTaskTypes)
                startUpTasks.Add(_containerManager.ResolveUnregistered(startUpTaskType) as IStartupTask);
            //sort
            startUpTasks = startUpTasks.AsQueryable().OrderBy(st => st.Order).ToList();
            foreach (IStartupTask startUpTask in startUpTasks)
                startUpTask.Execute();
        }

        private void InitializeContainer(ContainerConfigurer configurer, EventBroker broker)
        {
            var builder = new ContainerBuilder();
            //builder.InterceptInterfacesBy(new CallLogger());
            _containerManager = new ContainerManager(builder.Build());
            configurer.Configure(this, _containerManager, broker);

        }

        #endregion

        #region Methods

        /// <summary>
        ///     Initialize components and plugins in the nop environment.
        /// </summary>
        /// <param name="config">Config</param>
        public void Initialize()
        {
            //bool databaseInstalled = DataSettingsHelper.DatabaseIsInstalled();
            //if (databaseInstalled)
            //{
            //startup tasks
            RunStartupTasks();
            //}
        }

        public T Resolve<T>() where T : class
        {
            return ContainerManager.Resolve<T>();
        }

        public object Resolve(Type type, params object[] parameters)
        {
            return ContainerManager.Resolve(type, parameters);
        }

        public T[] ResolveAll<T>()
        {
            return ContainerManager.ResolveAll<T>();
        }

        #endregion

        #region Properties

        public ContainerManager ContainerManager
        {
            get { return _containerManager; }
        }

        #endregion
    }

    /// <summary>
    ///     Configures the inversion of control container with services used by Nop.
    /// </summary>
    public class ContainerConfigurer
    {
        public virtual void Configure(IEngine engine, ContainerManager containerManager, EventBroker broker)
        {
            //other dependencies
            //containerManager.AddComponentInstance<NopConfig>(configuration, "nop.configuration");
            containerManager.AddComponentInstance<IEngine>(engine, "odn.engine");
            containerManager.AddComponentInstance<ContainerConfigurer>(this, "odn.containerConfigurer");

            //type finder
            containerManager.AddComponent<ITypeFinder, WebAppTypeFinder>("odn.typeFinder");

            //register dependencies provided by other assemblies
            var typeFinder = containerManager.Resolve<ITypeFinder>();
            containerManager.UpdateContainer(x =>
            {
                IEnumerable<Type> drTypes = typeFinder.FindClassesOfType<IDependencyRegistrar>();
                var drInstances = new List<IDependencyRegistrar>();
                foreach (Type drType in drTypes)
                    drInstances.Add((IDependencyRegistrar)Activator.CreateInstance(drType));
                //sort
                drInstances = drInstances.AsQueryable().OrderBy(t => t.Order).ToList();
                foreach (IDependencyRegistrar dependencyRegistrar in drInstances)
                    dependencyRegistrar.Register(x, typeFinder);
            });

            //event broker
            containerManager.AddComponentInstance(broker);

            //service registration
            containerManager.AddComponent<DependencyAttributeRegistrator>("odn.serviceRegistrator");
            var registrator = containerManager.Resolve<DependencyAttributeRegistrator>();
            IEnumerable<AttributeInfo<DependencyAttribute>> services = registrator.FindServices();
            string[] configurations = GetComponentConfigurations();
            services = registrator.FilterServices(services, configurations);
            registrator.RegisterServices(services);
        }

        protected virtual string[] GetComponentConfigurations()
        {
            var configurations = new List<string>();
            string trustConfiguration = (CommonHelper.GetTrustLevel() > AspNetHostingPermissionLevel.Medium)
                ? ConfigurationKeys.FullTrust
                : ConfigurationKeys.MediumTrust;
            configurations.Add(trustConfiguration);
            return configurations.ToArray();
        }

        /// <summary>
        ///     Known configuration keys used to configure services.
        /// </summary>
        public static class ConfigurationKeys
        {
            /// <summary>Key used to configure services intended for medium trust.</summary>
            public const string MediumTrust = "MediumTrust";

            /// <summary>Key used to configure services intended for full trust.</summary>
            public const string FullTrust = "FullTrust";
        }
    }

    public static class AutofacExtensions
    {
        // DynamicProxy2 generator for creating proxies
        private static readonly ProxyGenerator generator = new ProxyGenerator();

        /// <summary>
        /// Intercept ALL registered interfaces with provided interceptors.
        /// Override Autofac activation with a Interface Proxy.
        /// Does not intercept classes, only interface bindings.
        /// </summary>
        /// <param name="builder">Contained builder to apply interceptions to.</param>
        /// <param name="interceptors">List of interceptors to apply.</param>
        public static void InterceptInterfacesBy(this ContainerBuilder builder, params IInterceptor[] interceptors)
        {
            builder.RegisterCallback((componentRegistry) =>
            {
                foreach (var registration in componentRegistry.Registrations)
                {
                    InterceptRegistration(registration, interceptors);
                }
            });
        }

        /// <summary>
        /// Intercept a specific component registrations.
        /// </summary>
        /// <param name="registration">Component registration</param>
        /// <param name="interceptors">List of interceptors to apply.</param>
        private static void InterceptRegistration(IComponentRegistration registration, params IInterceptor[] interceptors)
        {
            // proxy does not get allong well with Activated event and registrations with Activated events cannot be proxied.
            // They are casted to LimitedType in the IRegistrationBuilder OnActivated method. This is the offending Autofac code:
            // 
            // public IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> OnActivated(Action<IActivatedEventArgs<TLimit>> handler)
            // {
            //    if (handler == null) throw new ArgumentNullException("handler");
            //    RegistrationData.ActivatedHandlers.Add(
            //        (s, e) => handler(new ActivatedEventArgs<TLimit>(e.Context, e.Component, e.Parameters, (TLimit)e.Instance)));
            //    return this;
            // }
            Delegate[] handlers = GetActivatedEventHandlers(registration);
            if (handlers == null)
                return;
            if (handlers.Any(h => handlers[0].Method.DeclaringType.Namespace.StartsWith("Autofac")))
            {
                return;
            }

            registration.Activating += (sender, e) =>
            {
                Type type = e.Instance.GetType();

                if (e.Component.Services.OfType<IServiceWithType>().Any(swt => !swt.ServiceType.IsInterface || !swt.ServiceType.IsVisible) ||
                    // prevent proxying the proxy 
                    type.Namespace == "Castle.Proxies")
                {
                    return;
                }

                var proxiedInterfaces = type.GetInterfaces().Where(i => i.IsVisible).ToArray();

                if (!proxiedInterfaces.Any())
                {
                    return;
                }

                // intercept with all interceptors
                var theInterface = proxiedInterfaces.First();
                var interfaces = proxiedInterfaces.Skip(1).ToArray();

                e.Instance = generator.CreateInterfaceProxyWithTarget(theInterface, interfaces, e.Instance, interceptors);
            };
        }

        /// <summary>
        /// Get Activated event handlers for a registrations
        /// </summary>
        /// <param name="registration">Registration to retrieve events from</param>
        /// <returns>Array of delegates in the event handler</returns>
        private static Delegate[] GetActivatedEventHandlers(IComponentRegistration registration)
        {
            FieldInfo eventHandlerField = registration.GetType().GetField("Activated", BindingFlags.NonPublic | BindingFlags.Instance);
            var registrations = eventHandlerField.GetValue(registration);
            System.Diagnostics.Debug.WriteLine(registrations);

            return registrations == null ? null : registrations.GetType().GetMethod("GetInvocationList").Invoke(registrations, null) as Delegate[];
        }
    }
}